# -*- coding: utf-8 -*-

import os
import logging
from odoo.tools.translate import _
from odoo import api, fields, models, tools
from odoo.exceptions import UserError, ValidationError

CONSECUTIVE_TIMEOUT_FOR_FAILURE = 3


class CompletionStatus:  # inherit from enum.StrEnum in 3.11
    FULLY_DONE = 'fully done'
    PARTIALLY_DONE = 'partially done'
    FAILED = 'failed'


_logger = logging.getLogger(__name__)


class View(models.Model):
    _inherit = 'ir.ui.view'

    @api.model
    def _render_template(self, template, values=None):
        param_sudo = self.env['ir.config_parameter'].sudo()
        if template in ['web.login', 'web.webclient_bootstrap']:
            if not values:
                values = {}
            values["title"] = param_sudo.get_param("web.window.title", "Justdoo")
        return super()._render_template(template, values)


class ResLang(models.Model):
    _inherit = 'res.lang'

    @api.model
    def refresh_default_lang(self):
        zh_lang = self.env['res.lang'].sudo().search([('code', '=', 'zh_CN')], limit=1)
        if zh_lang:
            zh_lang.sudo().write({'date_format': '%Y-%m-%d', 'time_format': '%H:%M:%S'})


class Users(models.Model):
    _inherit = 'res.users'

    @api.model
    def default_get(self, field_list):
        values = super(Users, self).default_get(field_list)
        if not values.get('tz', False):
            values['tz'] = 'Asia/Shanghai'
        return values

    @api.model_create_multi
    def create(self, value_list):
        users = super(Users, self).create(value_list)
        none_tz_users = users.filtered(lambda user: not user.tz)
        if none_tz_users and len(none_tz_users) > 0:
            none_tz_users.write({'tz': 'Asia/Shanghai'})
        return users


class Partner(models.Model):
    _inherit = 'res.partner'

    customer = fields.Boolean(string="Customer")
    supplier = fields.Boolean(string="Supplier")


class ir_cron(models.Model):
    _inherit = 'ir.cron'

    @classmethod
    def _process_job(cls, db, cron_cr, job):
        enable_cron = tools.config.get('enable_cron', False)
        if not enable_cron:
            # 当前实例未启用自动化任务
            return False
        """
        Execute the cron's server action in a dedicated transaction.

        In case the previous process actually timed out, the cron's
        server action is not executed and the cron is considered
        ``'failed'``.

        The server action can use the progress API via the method
        :meth:`_notify_progress` to report processing progress, i.e. how
        many records are done and how many records are remaining to
        process.

        Those progress notifications are used to determine the job's
        ``CompletionStatus`` and to determine the next time the cron
        will be executed:

        - ``'fully done'``: the cron is rescheduled later, it'll be
          executed again after its regular time interval or upon a new
          trigger.

        - ``'partially done'``: the cron is rescheduled ASAP, it'll be
          executed again by this or another cron worker once the other
          ready cron jobs have been executed.

        - ``'failed'``: the cron is deactivated if it failed too many
          times over a given time span; otherwise it is rescheduled
          later.
        """
        env = api.Environment(cron_cr, job['user_id'], {})
        ir_cron = env[cls._name]

        failed_by_timeout = (
                job['timed_out_counter'] >= CONSECUTIVE_TIMEOUT_FOR_FAILURE
                and not job['done']
        )

        if not failed_by_timeout:
            status = cls._run_job(job)
        else:
            status = CompletionStatus.FAILED
            cron_cr.execute("""
                UPDATE ir_cron_progress
                SET timed_out_counter = 0
                WHERE id = %s
            """, (job['progress_id'],))
            _logger.error("Job %r (%s) timed out", job['cron_name'], job['id'])

        ir_cron._update_failure_count(job, status)

        if status in (CompletionStatus.FULLY_DONE, CompletionStatus.FAILED):
            ir_cron._reschedule_later(job)
        elif status == CompletionStatus.PARTIALLY_DONE:
            ir_cron._reschedule_asap(job)
            if os.getenv('ODOO_NOTIFY_CRON_CHANGES'):
                cron_cr.postcommit.add(ir_cron._notifydb)  # See: `_notifydb`
        else:
            raise RuntimeError("unreachable")

        cron_cr.commit()


class IRSequence(models.Model):
    _inherit = 'ir.sequence'

    loop_time = fields.Datetime(string='Last Loop Time', default=fields.Datetime.now)
    loop_type = fields.Selection(selection=[('year', 'Year'), ('month', 'Month'), ('day', 'Day')], string='Loop Type')

    @api.model
    def next_by_code(self, sequence_code, sequence_date=None):
        user = self.env.user
        company_id = self.env.company.id if not user else user.company_id.id
        seq_domain = [('code', '=', sequence_code), ('company_id', 'in', [company_id, False])]
        temp_sequence = self.search(seq_domain, order='company_id', limit=1)
        if not temp_sequence or not temp_sequence.loop_type:
            return super().next_by_code(sequence_code, sequence_date)
        tz_now_time = fields.Datetime.context_timestamp(temp_sequence, fields.Datetime.now())
        tz_loop_time = fields.Datetime.context_timestamp(temp_sequence, temp_sequence.loop_time)
        if temp_sequence.loop_type == 'year' and tz_now_time.year != tz_loop_time.year:
            temp_sequence.sudo().write({'loop_time': fields.Datetime.now(), 'number_next': 1})
            return super().next_by_code(sequence_code, sequence_date)
        if temp_sequence.loop_type == 'month' and tz_now_time.month != tz_loop_time.month:
            temp_sequence.sudo().write({'loop_time': fields.Datetime.now(), 'number_next': 1})
            return super().next_by_code(sequence_code, sequence_date)
        if temp_sequence.loop_type == 'day' and tz_now_time.day != tz_loop_time.day:
            temp_sequence.sudo().write({'loop_time': fields.Datetime.now(), 'number_next': 1})
            return super().next_by_code(sequence_code, sequence_date)
        return super().next_by_code(sequence_code, sequence_date)

    def next_by_id(self, sequence_date=None):
        self.browse().check_access('read')
        temp_sequence = self
        tz_now_time = fields.Datetime.context_timestamp(temp_sequence, fields.Datetime.now())
        tz_loop_time = fields.Datetime.context_timestamp(temp_sequence, temp_sequence.loop_time)
        if temp_sequence.loop_type == 'year' and tz_now_time.year != tz_loop_time.year:
            temp_sequence.sudo().write({'loop_time': fields.Datetime.now(), 'number_next': 1})
            return super().next_by_id(sequence_date)
        if temp_sequence.loop_type == 'month' and tz_now_time.month != tz_loop_time.month:
            temp_sequence.sudo().write({'loop_time': fields.Datetime.now(), 'number_next': 1})
            return super().next_by_id(sequence_date)
        if temp_sequence.loop_type == 'day' and tz_now_time.day != tz_loop_time.day:
            temp_sequence.sudo().write({'loop_time': fields.Datetime.now(), 'number_next': 1})
            return super().next_by_id(sequence_date)
        return super().next_by_id(sequence_date)

    def get_sequence_prefix_suffix(self, date=None, date_range=None):
        def _interpolate(s, d):
            return (s % d) if s else ''

        def _interpolation_dict():
            temp_datetime = fields.Datetime.now()
            now = range_date = effective_date = fields.Datetime.context_timestamp(self, temp_datetime)
            # now = range_date = effective_date = datetime.now(pytz.timezone(self._context.get('tz') or 'UTC'))
            if date or self._context.get('ir_sequence_date'):
                effective_date = fields.Datetime.from_string(date or self._context.get('ir_sequence_date'))
            if date_range or self._context.get('ir_sequence_date_range'):
                range_date = fields.Datetime.from_string(date_range or self._context.get('ir_sequence_date_range'))

            sequences = {
                'year': '%Y', 'month': '%m', 'day': '%d', 'y': '%y', 'doy': '%j', 'woy': '%W',
                'weekday': '%w', 'h24': '%H', 'h12': '%I', 'min': '%M', 'sec': '%S'
            }
            res = {}
            for key, formatter in sequences.items():
                res[key] = effective_date.strftime(formatter)
                res['range_' + key] = range_date.strftime(formatter)
                res['current_' + key] = now.strftime(formatter)
            return res

        self.ensure_one()
        d = _interpolation_dict()
        try:
            interpolated_prefix = _interpolate(self.prefix, d)
            interpolated_suffix = _interpolate(self.suffix, d)
        except ValueError:
            raise UserError(_('Invalid prefix or suffix for sequence %r', self.name))
        return interpolated_prefix, interpolated_suffix

    def get_next_char(self, number_next):
        interpolated_prefix, interpolated_suffix = self.get_sequence_prefix_suffix()
        next_code = interpolated_prefix + '%%0%sd' % self.padding % number_next + interpolated_suffix
        return next_code
